from fastapi import FastAPI, Body
from fastapi import Request
from pydantic import BaseModel
import os, sys

import oss2
import socket
import requests, time
import hashlib
import json
import zipfile

import ezdxf
import math
import numpy as np

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)

from app.floorplan2ifc import IFCBuilder, Point, sign
from xml.dom.minidom import parse
import uuid
import itertools

app = FastAPI()


class Xml(BaseModel):
    # 必要参数
    xml: str
    # 可选参数


class Config:
    deviceId = socket.gethostname()
    appId = "k9CKFzI6vcC5Si5p"
    appSecret = "eHRi@yaImM@nil0mFWw86QvhYB!59OK$"
    bucket_name = "unre-ucl360"
    endpoint = "https://oss-cn-hangzhou.aliyuncs.com"


def getToken(deviceId, timestamp, params, appId, appSecret):
    str1 = appId + deviceId + timestamp + params
    str2 = str1 + appSecret
    token = hashlib.md5(str2.encode("utf-8"))
    return token.hexdigest()


async def get_ifc(xml):
    res = {"statusCode": "200", "message": "Upload success", "filePath": ""}
    filename = xml
    # region 下载xml文件
    timestamp = str(round(time.time() * 1000))
    token = getToken(Config.deviceId, timestamp, "", Config.appId, Config.appSecret)
    data = {"deviceId": Config.deviceId, "timestamp": timestamp, "params": "", "appId": Config.appId, "token": token}
    try:
        url = "https://api.zhihuiliang.com" + "/open/storage/aliyun/getAccessToken"
        r = requests.post(url, data=data, verify=False)
        data = json.loads(r.json()['data'])

        # 首先初始化AccessKeyId、AccessKeySecret、Endpoint等信息。
        # 通过环境变量获取，或者把诸如“<你的AccessKeyId>”替换成真实的AccessKeyId等。
        #
        # 以杭州区域为例，Endpoint可以是：
        #   http://oss-cn-hangzhou.aliyuncs.com
        #   https://oss-cn-hangzhou.aliyuncs.com
        # 分别以HTTP、HTTPS协议访问。
        access_key_id = data["accessKeyId"]
        access_key_secret = data['accessKeySecret']
        security_token = data['securityToken']

        # 确认上面的参数都填写正确了
        for param in (access_key_id, access_key_secret, Config.bucket_name, Config.endpoint):
            assert '<' not in param, '请设置参数：' + param

        # 创建Bucket对象，所有Object相关的接口都可以通过Bucket对象来进行
        auth = oss2.StsAuth(access_key_id, access_key_secret, security_token)
        bucket = oss2.Bucket(auth, Config.endpoint, Config.bucket_name)

        # key = "unre/5/168_1613958943150_roomcontour.xml"
        filename = xml.split("/")[-1]
        ifc_filename = filename.replace(".xml", ".ifc")

        result = bucket.get_object_to_file(xml, filename)
    except:
        res['statusCode'] = 500
        res['message'] = "Download xml fail"
        return res

    size = os.path.getsize(filename)
    if size == 0:
        res['statusCode'] = 500
        res['message'] = "xml file is empty"
        return res
    # endregion

    # region 解析xml文件
    height = 3000
    thickness = 200

    # homepath = os.environ['HOMEPATH']
    # filename = homepath + '\\Desktop\\roomcontour.xml'

    # inputFile = "C:\\Users\\yumiao\\Desktop\\roomcontour.xml"
    domTree = parse(filename)

    # 文档根元素
    rootNode = domTree.documentElement
    room_infos = rootNode.getElementsByTagName("RoomInfo")
    room_height = room_infos[0].getAttribute('Height')
    height = float(room_height) if room_height else height

    wall_infos = rootNode.getElementsByTagName("WallInfo")
    wall_info = []
    for wall in wall_infos:
        lines = wall.getElementsByTagName("LineData")
        for line in lines:
            lineData = {}
            try:
                lineData['@StartX'] = line.getAttribute("StartX")
                if not lineData['@StartX']:
                    continue
                lineData['@StartY'] = line.getAttribute("StartY")
                lineData['@StartZ'] = line.getAttribute("StartZ")
                lineData['@EndX'] = line.getAttribute("EndX")
                lineData['@EndY'] = line.getAttribute("EndY")
                lineData['@EndZ'] = line.getAttribute("EndZ")
                lineData['@WallId'] = line.getAttribute("WallId")
                lineData['@Height'] = line.getAttribute("Height")
            except:
                continue
            wall_info.append(lineData)
    # if not wall_info:
    #     res['statusCode'] = '500'
    #     res['message'] = 'XML parse failed, XML version unmatch'
    #     return res
    floor_infos = rootNode.getElementsByTagName("FloorInfo")
    floor_info = []
    for floor in floor_infos:
        floor_shapes = floor.getElementsByTagName('FloorShape')
        for floor_shape in floor_shapes:
            lines = floor_shape.getElementsByTagName('LineData')
            for line in lines:
                lineData = {}
                try:
                    lineData['@StartX'] = line.getAttribute("StartX")
                    if not lineData['@StartX']:
                        continue
                    lineData['@StartY'] = line.getAttribute("StartY")
                    lineData['@StartZ'] = line.getAttribute("StartZ")
                    lineData['@EndX'] = line.getAttribute("EndX")
                    lineData['@EndY'] = line.getAttribute("EndY")
                    lineData['@EndZ'] = line.getAttribute("EndZ")
                    lineData['@WallId'] = line.getAttribute("WallId")
                    lineData['@Height'] = line.getAttribute("Height")
                except:
                    continue
                floor_info.append(lineData)

    door_infos = rootNode.getElementsByTagName("DoorInfo")
    door_info = []
    for door in door_infos:
        lines = door.getElementsByTagName('DoorData')
        for line in lines:
            lineData = {}
            try:
                lineData['@StartX'] = line.getAttribute("StartX")
                if not lineData['@StartX']:
                    continue
                lineData['@StartY'] = line.getAttribute("StartY")
                lineData['@StartZ'] = line.getAttribute("StartZ")
                lineData['@EndX'] = line.getAttribute("EndX")
                lineData['@EndY'] = line.getAttribute("EndY")
                lineData['@EndZ'] = line.getAttribute("EndZ")
                lineData['@WallId'] = line.getAttribute("WallId")
                lineData['@Height'] = line.getAttribute("Height")
            except:
                continue
            door_info.append(lineData)

    win_infos = rootNode.getElementsByTagName("WinInfo")
    win_info = []
    for win in win_infos:
        lines = win.getElementsByTagName('WinData')
        for line in lines:
            lineData = {}
            try:
                lineData['@StartX'] = line.getAttribute("StartX")
                if not lineData['@StartX']:
                    continue
                lineData['@StartY'] = line.getAttribute("StartY")
                lineData['@StartZ'] = line.getAttribute("StartZ")
                lineData['@EndX'] = line.getAttribute("EndX")
                lineData['@EndY'] = line.getAttribute("EndY")
                lineData['@EndZ'] = line.getAttribute("EndZ")
                lineData['@WallId'] = line.getAttribute("WallId")
                lineData['@Height'] = line.getAttribute("Height")
            except:
                continue
            win_info.append(lineData)
    # endregion

    # region 创建IFC文件
    builder = IFCBuilder(ifc_filename)
    wall_dict = {}
    for wall in wall_info:
        start_point = Point(float(wall['@StartX']), float(wall['@StartY']), float(wall['@StartZ']))
        end_point = Point(float(wall['@EndX']), float(wall['@EndY']), float(wall['@EndZ']))
        wall_dir = (end_point - start_point).normalize()
        outter_dir = Point.z().cross(wall_dir)
        outter_dir = outter_dir * sign((start_point - builder.scan_point).dot(outter_dir))
        outter_dir = outter_dir.normalize()
        # using the start point of the first wall as the origin of site
        wall_obj = builder.create_wall(wall['@WallId'], start_point, end_point, height, thickness, outter_dir)
        wall_dict[wall['@WallId']] = (wall_obj, wall_dir, outter_dir)
    for win in win_info:
        start_point = Point(float(win['@StartX']), float(win['@StartY']), float(win['@StartZ']))
        end_point = Point(float(win['@EndX']), float(win['@EndY']), float(win['@EndZ']))
        builder.create_window(str(uuid.uuid4()), start_point, end_point, float(win['@Height']), 300,
                              *wall_dict[win['@WallId']])
    for door in door_info:
        start_point = Point(float(door['@StartX']), float(door['@StartY']), float(door['@StartZ']))
        end_point = Point(float(door['@EndX']), float(door['@EndY']), float(door['@EndZ']))
        builder.create_door(str(uuid.uuid4()), start_point, end_point, float(door['@Height']), 200,
                            *wall_dict[door['@WallId']])
    builder.write()
    # endregion

    # region 上传IFC文件
    ifc_object_name = xml.replace(".xml", ".ifc")
    try:
        result = bucket.put_object_from_file(ifc_object_name, ifc_filename)
    except:
        res['statusCode'] = 500
        res['message'] = "Upload ifc fail"
        return res
    res['statusCode'] = result.status
    res['filePath'] = ifc_object_name
    # endregion

    # region 删除临时文件
    os.remove(filename)
    os.remove(ifc_filename)
    # endregion
    return res


async def get_kjl_json(xml):
    res = {"statusCode": "200", "message": "Upload success", "filePath": ""}
    filename = xml
    timestamp = str(round(time.time() * 1000))
    token = getToken(Config.deviceId, timestamp, "", Config.appId, Config.appSecret)
    data = {"deviceId": Config.deviceId, "timestamp": timestamp, "params": "", "appId": Config.appId, "token": token}
    try:
        url = "https://api.zhihuiliang.com" + "/open/storage/aliyun/getAccessToken"
        r = requests.post(url, data=data, verify=False)
        data = json.loads(r.json()['data'])

        # 首先初始化AccessKeyId、AccessKeySecret、Endpoint等信息。
        # 通过环境变量获取，或者把诸如“<你的AccessKeyId>”替换成真实的AccessKeyId等。
        #
        # 以杭州区域为例，Endpoint可以是：
        #   http://oss-cn-hangzhou.aliyuncs.com
        #   https://oss-cn-hangzhou.aliyuncs.com
        # 分别以HTTP、HTTPS协议访问。
        access_key_id = data["accessKeyId"]
        access_key_secret = data['accessKeySecret']
        security_token = data['securityToken']

        # 确认上面的参数都填写正确了
        for param in (access_key_id, access_key_secret, Config.bucket_name, Config.endpoint):
            assert '<' not in param, '请设置参数：' + param

        # 创建Bucket对象，所有Object相关的接口都可以通过Bucket对象来进行
        auth = oss2.StsAuth(access_key_id, access_key_secret, security_token)
        bucket = oss2.Bucket(auth, Config.endpoint, Config.bucket_name)

        # key = "unre/5/168_1613958943150_roomcontour.xml"
        filename = xml.split("/")[-1]
        kjl_filename = filename.replace(".xml", "_kjl.json")

        result = bucket.get_object_to_file(xml, filename)
    except:
        res['statusCode'] = 500
        res['message'] = "Download xml fail"
        return res

    size = os.path.getsize(filename)
    if size == 0:
        res['statusCode'] = 500
        res['message'] = "xml file is empty"
        return res

    domTree = parse(filename)
    # 文档根元素
    rootNode = domTree.documentElement
    wall_infos = rootNode.getElementsByTagName("WallInfo")
    wall_info = []
    room_infos = rootNode.getElementsByTagName("RoomInfo")
    room_height = room_infos[0].getAttribute('Height')
    for wall in wall_infos:
        lines = wall.getElementsByTagName("LineData")
        for line in lines:
            lineData = {}
            lineData['@StartX'] = line.getAttribute("StartX")
            lineData['@StartY'] = line.getAttribute("StartY")
            lineData['@StartZ'] = line.getAttribute("StartZ")
            lineData['@EndX'] = line.getAttribute("EndX")
            lineData['@EndY'] = line.getAttribute("EndY")
            lineData['@EndZ'] = line.getAttribute("EndZ")
            lineData['@WallId'] = line.getAttribute("WallId")
            lineData['@Height'] = line.getAttribute("Height")
            wall_info.append(lineData)

    floor_infos = rootNode.getElementsByTagName("FloorInfo")
    floor_info = []
    for floor in floor_infos:
        floor_shapes = floor.getElementsByTagName('FloorShape')
        for floor_shape in floor_shapes:
            lines = floor_shape.getElementsByTagName('LineData')
            for line in lines:
                lineData = {}
                lineData['@StartX'] = line.getAttribute("StartX")
                lineData['@StartY'] = line.getAttribute("StartY")
                lineData['@StartZ'] = line.getAttribute("StartZ")
                lineData['@EndX'] = line.getAttribute("EndX")
                lineData['@EndY'] = line.getAttribute("EndY")
                lineData['@EndZ'] = line.getAttribute("EndZ")
                lineData['@WallId'] = line.getAttribute("WallId")
                lineData['@Height'] = line.getAttribute("Height")
                floor_info.append(lineData)

    door_infos = rootNode.getElementsByTagName("DoorInfo")
    door_info = []
    for door in door_infos:
        lines = door.getElementsByTagName('DoorData')
        for line in lines:
            lineData = {}
            lineData['@StartX'] = line.getAttribute("StartX")
            lineData['@StartY'] = line.getAttribute("StartY")
            lineData['@StartZ'] = line.getAttribute("StartZ")
            lineData['@EndX'] = line.getAttribute("EndX")
            lineData['@EndY'] = line.getAttribute("EndY")
            lineData['@EndZ'] = line.getAttribute("EndZ")
            lineData['@WallId'] = line.getAttribute("WallId")
            lineData['@Height'] = line.getAttribute("Height")
            door_info.append(lineData)

    win_infos = rootNode.getElementsByTagName("WinInfo")
    win_info = []
    for win in win_infos:
        lines = win.getElementsByTagName('WinData')
        for line in lines:
            lineData = {}
            lineData['@StartX'] = line.getAttribute("StartX")
            lineData['@StartY'] = line.getAttribute("StartY")
            lineData['@StartZ'] = line.getAttribute("StartZ")
            lineData['@EndX'] = line.getAttribute("EndX")
            lineData['@EndY'] = line.getAttribute("EndY")
            lineData['@EndZ'] = line.getAttribute("EndZ")
            lineData['@WallId'] = line.getAttribute("WallId")
            lineData['@Height'] = line.getAttribute("Height")
            win_info.append(lineData)

    holes = []
    for a_door in door_info:
        door_dict = {}
        door_dict['type'] = 'DOOR'
        door_dict['openSide'] = 'LEFT'
        door_dict['height'] = float(a_door['@Height']) / 1000
        start = {'x': float(a_door['@StartX']) / 1000, 'y': float(a_door['@StartY']) / 1000}
        end = {'x': float(a_door['@EndX']) / 1000, 'y': float(a_door['@EndY']) / 1000}
        door_dict['start'] = start
        door_dict['end'] = end
        door_dict['sillHeight'] = None
        door_dict['groundClearance'] = None
        holes.append(door_dict)

    for a_win in win_info:
        win_dict = {}
        win_dict['type'] = 'WINDOW'
        win_dict['openSide'] = 'LEFT'
        win_dict['height'] = float(a_win['@Height']) / 1000
        start = {'x': float(a_win['@StartX']) / 1000, 'y': float(a_win['@StartY']) / 1000}
        end = {'x': float(a_win['@EndX']) / 1000, 'y': float(a_win['@EndY']) / 1000}
        win_dict['start'] = start
        win_dict['end'] = end
        win_dict['sillHeight'] = None
        win_dict['groundClearance'] = None
        holes.append(win_dict)
    pos_x, pos_y = 0.0, 0.0
    walls = []
    for a_wall in wall_info:
        wall_dict = {}
        wall_dict['type'] = 'LINE'
        wall_dict['control'] = {"x": 0, "y": 0}
        wall_dict['height'] = float(room_height) / 1000
        wall_dict["thickness"] = 0.15
        wall_dict["bulge"] = 0
        wall_dict["loadBearingWall"] = None
        start = {'x': float(a_wall['@StartX']) / 1000, 'y': float(a_wall['@StartY']) / 1000}
        end = {'x': float(a_wall['@EndX']) / 1000, 'y': float(a_wall['@EndY']) / 1000}
        pos_x = pos_x + start['x'] + end['x']
        pos_y = pos_y + start['y'] + end['y']
        wall_dict['start'] = start
        wall_dict['end'] = end
        walls.append(wall_dict)
    position = {'x': pos_x / ((len(wall_info) * 2)), 'y': pos_y / ((len(wall_info) * 2))}
    name = filename.split(".")[0]
    rooms = [{"name": name, "position": position}]

    kjl_json = {"name": None, "rooms": rooms, "holes": holes, "height": None, "walls": walls}
    json_name = name + "_kjl.json"
    with open(json_name, 'w+', encoding='utf8') as f:
        json.dump(kjl_json, f)
    kjl_object_name = xml.replace(".xml", "_kjl.json")
    print(kjl_filename)
    print(kjl_object_name)
    try:
        result = bucket.put_object_from_file(kjl_object_name, kjl_filename)
    except:
        res['statusCode'] = 500
        res['message'] = "Upload ifc fail"
        return res
    res['statusCode'] = result.status
    res['filePath'] = kjl_object_name

    os.remove(filename)
    os.remove(kjl_filename)
    return res


async def get_dxf(xml):
    res = {"statusCode": "200", "message": "Upload success", "filePath": ""}
    filename = xml
    timestamp = str(round(time.time() * 1000))
    token = getToken(Config.deviceId, timestamp, "", Config.appId, Config.appSecret)
    data = {"deviceId": Config.deviceId, "timestamp": timestamp, "params": "", "appId": Config.appId, "token": token}
    try:
        url = "https://api.zhihuiliang.com" + "/open/storage/aliyun/getAccessToken"
        r = requests.post(url, data=data, verify=False)
        data = json.loads(r.json()['data'])

        # 首先初始化AccessKeyId、AccessKeySecret、Endpoint等信息。
        # 通过环境变量获取，或者把诸如“<你的AccessKeyId>”替换成真实的AccessKeyId等。
        #
        # 以杭州区域为例，Endpoint可以是：
        #   http://oss-cn-hangzhou.aliyuncs.com
        #   https://oss-cn-hangzhou.aliyuncs.com
        # 分别以HTTP、HTTPS协议访问。
        access_key_id = data["accessKeyId"]
        access_key_secret = data['accessKeySecret']
        security_token = data['securityToken']

        # 确认上面的参数都填写正确了
        for param in (access_key_id, access_key_secret, Config.bucket_name, Config.endpoint):
            assert '<' not in param, '请设置参数：' + param

        # 创建Bucket对象，所有Object相关的接口都可以通过Bucket对象来进行
        auth = oss2.StsAuth(access_key_id, access_key_secret, security_token)
        bucket = oss2.Bucket(auth, Config.endpoint, Config.bucket_name)

        # key = "unre/5/168_1613958943150_roomcontour.xml"
        filename = xml.split("/")[-1]
        dxf_filename = filename.replace(".xml", ".dxf")

        result = bucket.get_object_to_file(xml, filename)
    except:
        res['statusCode'] = 500
        res['message'] = "Download xml fail"
        return res

    try:
        zip_filename = filename.replace("_roomcontour.xml", ".zip")
        zip_key = xml.replace("_roomcontour.xml", ".zip")
        result = bucket.get_object_to_file(zip_key, zip_filename)
    except:
        res['statusCode'] = 500
        res['message'] = "Download zip fail"
        return res

    size = os.path.getsize(filename)
    if size == 0:
        res['statusCode'] = 500
        res['message'] = "xml file is empty"
        return res

    size = os.path.getsize(zip_filename)
    if size == 0:
        res['statusCode'] = 500
        res['message'] = "zip file is empty"
        return res

    zip_file = zipfile.ZipFile(zip_filename)
    zip_list = zip_file.namelist()
    json_name = None
    for f in zip_list:
        if ".json" in f and "roomType" not in f:
            json_name = f
            zip_file.extract(f, ".")
    zip_prefix = zip_filename.replace(".zip", "")
    json_path = json_name

    # homepath = os.environ['HOMEPATH']
    # filename = homepath + '\\Desktop\\roomcontour.xml'

    # inputFile = "C:\\Users\\yumiao\\Desktop\\roomcontour.xml"
    doc = ezdxf.new()
    msp = doc.modelspace()

    domTree = parse(filename)
    # json_path = r"E:\code\xml2dxf\app\test\5b31ef3f9d9c3c2d95a779599f7ee1b4_ruixin.json"
    with open(json_path, "r") as f:
        json_data = json.load(f)
    # 文档根元素
    rootNode = domTree.documentElement
    print(rootNode.nodeName)

    my_line_types = [
        ("DOTTED", "Dotted .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .", [0.2, 0.0, -0.2]),
        ("DOTTEDX2", "Dotted (2x) .    .    .    .    .    .    .    . ", [15, 15, -15]),
        ("DOTTED2", "Dotted (.5) . . . . . . . . . . . . . . . . . . . ", [0.1, 0.0, -0.1]),
    ]
    for name, desc, pattern in my_line_types:
        if name not in doc.linetypes:
            doc.linetypes.new(name=name, dxfattribs={'description': desc, 'pattern': pattern})

    door = doc.blocks.new(name='door')
    ld0 = door.add_arc((-50, 0), 100, 0, 90)
    ld0.set_dxf_attrib('linetype', "DOTTEDX2")
    ld1 = door.add_line((-50, 0), (-50, 100))
    ld1.set_dxf_attrib('linetype', "DOTTEDX2")
    ld2 = door.add_line((-50, 0), (50, 0))

    win = doc.blocks.new(name='win')
    lw0 = win.add_lwpolyline([(-0.5, 0), (0.5, 0), (0.5, 0.5), (-0.5, 0.5), (-0.5, -0.5), (0.5, -0.5), (0.5, 0)])

    WallInfo = rootNode.getElementsByTagName("WallInfo")[0]
    wall_lines = WallInfo.getElementsByTagName("LineData")
    rotate_info = rootNode.getElementsByTagName("Rotate")[0]
    z_rotate = eval(rotate_info.getAttribute("Rx"))
    rad = -z_rotate / 180 * math.pi
    # my_layer = doc.layers.new('Wall')
    walls_dict = {}

    class Line:
        def __init__(self, line):
            self.line = line
            self.id = None
            self.direction = None
            self.bump_point = None
            self.offset = 0
            self.wall_info = {"bump_mapping_list": []}
            self.a, self.b, self.angle = _get_a_b_by_line(self)

        def __str__(self):
            return str(self.line)

        __repr__ = __str__

        def __iter__(self):
            """
            通过代理line的迭代，yield元素
            :return:
            """
            for point in self.line:
                yield point

        def __getitem__(self, ind):
            return self.line[ind]

        def __len__(self):
            return len(self.line)

    def srotate(angle, valuex, valuey, pointx=0, pointy=0):
        # 绕点point旋转angle度
        valuex = np.array(valuex)
        valuey = np.array(valuey)
        sRotatex = (valuex - pointx) * math.cos(angle) + (valuey - pointy) * math.sin(angle) + pointx
        sRotatey = (valuey - pointy) * math.cos(angle) - (valuex - pointx) * math.sin(angle) + pointy
        return sRotatex, sRotatey

    def move_line(sx, sy, ex, ey, distance, direction):
        # 朝direction方向移动distance距离
        h = ((ex - sx) ** 2 + (ey - sy) ** 2) ** 0.5
        if (direction == 'S') or (direction == 'W' and (ex - sx) * (ey - sy) < 0) or (
                direction == 'E' and (ex - sx) * (ey - sy) > 0):
            distance = -distance
        x = distance * (abs(ey - sy) / h)
        y = distance * (abs(ex - sx) / h)
        if (ex - sx) * (ey - sy) > 0:
            return sx - x, sy + y, ex - x, ey + y
        else:
            return sx + x, sy + y, ex + x, ey + y

    def move_vertical_line_point(line, point, distance, direction):
        point1, point2 = line[0], line[1]
        sx, sy, ex, ey = point1[0], point1[1], point2[0], point2[1]
        # 将点朝线的垂直方向direction移动distance距离
        h = ((ex - sx) ** 2 + (ey - sy) ** 2) ** 0.5
        if (direction == 'S') or (direction == 'W' and (ex - sx) * (ey - sy) < 0) or (
                direction == 'E' and (ex - sx) * (ey - sy) > 0):
            distance = -distance
        x = distance * (abs(ey - sy) / h)
        y = distance * (abs(ex - sx) / h)
        if (ex - sx) * (ey - sy) > 0:
            point[0], point[1] = point[0] - x, point[1] + y
        else:
            point[0], point[1] = point[0] + x, point[1] + y
        return point

    def point_to_line(x1, y1, a, b):
        print(x1, y1, a, b)
        # 点映射到线
        if a != 0:
            x = ((x1 / a) + y1 - b) / ((1 / a) + a)
        else:
            x = x1
        # print(444, a, b)
        y = a * x + b
        if a == float('inf'):
            x = b
            y = y1
        return x, y

    def line_to_line(line, a, b):
        # line映射到线
        line[0][0], line[0][1] = point_to_line(line[0][0], line[0][1], a, b)
        line[1][0], line[1][1] = point_to_line(line[1][0], line[1][1], a, b)
        return line

    def get_b_by_a_and_point(point, a):
        # 计算截距
        x = point[0]
        y = point[1]
        b = y - a * x
        if a == float('inf'):
            b = x
        return b

    def get_midpoint_by_line(line):
        # 计算线段中点
        point1, point2 = line[0], line[1]
        x1, y1, x2, y2 = point1[0], point1[1], point2[0], point2[1]
        x = (x1 + x2) / 2
        y = (y1 + y2) / 2
        return x, y

    def get_lenth_of_line(line):
        point1, point2 = line[0], line[1]
        return get_distance_of_points(point1, point2)

    def get_distance_of_points(point1, point2):
        x1, y1, x2, y2 = point1[0], point1[1], point2[0], point2[1]
        return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5

    def get_longest_line(lines):
        # 找出最长的线段
        longest_line = None
        longest_value = 0
        for line in lines:
            lenth = get_lenth_of_line(line)
            if lenth > longest_value:
                longest_line = line
        return longest_line

    def _get_a_b_by_line(line):
        # 计算线段斜率和截距,y轴夹角
        point1, point2 = line[0], line[1]
        x1, y1, x2, y2 = point1[0], point1[1], point2[0], point2[1]
        if (x2 - x1) != 0:
            # a = round((y2-y1)/(x2-x1), 9)
            a = (y2 - y1) / (x2 - x1)
            if (y2 - y1) != 0:
                # angle = round(np.arctan((x2-x1)/(y2-y1)), 9)
                angle = np.arctan((x2 - x1) / (y2 - y1))
            else:
                angle = math.pi / 2
            b = y2 - a * x2
        else:
            # a = None
            a = float('inf')
            b = x1
            angle = 0
        angle = _deal_angle(angle)
        print("angle", angle, a)
        # if a == 0:
        #     a = 1/float('inf')
        return a, b, angle

    def _deal_angle(angle):
        if angle > math.pi / 2:
            angle = angle - math.pi
        elif angle <= -math.pi / 2:
            angle = angle + math.pi
        return angle

    def merge_point(line1, line2):
        # 如果端点接近则合并
        # 判断两线段是否有端点接近
        line_mean_lenth = (get_lenth_of_line(line1) + get_lenth_of_line(line2)) / 2
        for i in itertools.product(line1, line2):
            point1, point2 = i
            d = get_distance_of_points(point1, point2)
            if d < line_mean_lenth / 10:
                cpoint = cross_point(line1, line2)
                if not cpoint:
                    break
                print(cpoint, 11111)
                for a_point in line1:
                    if a_point in i:
                        a_point[0], a_point[1] = cpoint[0], cpoint[1]
                for a_point in line2:
                    if a_point in i:
                        a_point[0], a_point[1] = cpoint[0], cpoint[1]
                return line1, line2
        return False

    def cross_point(line1, line2):
        # 计算交点函数
        if line1[0][0] - line1[1][0] == 0:
            line1, line2 = line2, line1
        if line1[0][0] - line1[1][0] == 0:
            return None
        x1 = line1[0][0]  # 取四点坐标
        y1 = line1[0][1]
        x2 = line1[1][0]
        y2 = line1[1][1]

        x3 = line2[0][0]
        y3 = line2[0][1]
        x4 = line2[1][0]
        y4 = line2[1][1]

        print(x2 - x1, x3 - x4)
        k1 = (y2 - y1) * 1.0 / (x2 - x1)  # 计算k1,由于点均为整数，需要进行浮点数转化
        b1 = y1 * 1.0 - x1 * k1 * 1.0  # 整型转浮点型是关键
        if (x4 - x3) == 0:  # L2直线斜率不存在操作
            k2 = None
            b2 = 0
        else:
            k2 = (y4 - y3) * 1.0 / (x4 - x3)  # 斜率存在操作
            b2 = y3 * 1.0 - x3 * k2 * 1.0
        if k2 == None:
            x = x3
        else:
            x = (b2 - b1) * 1.0 / (k1 - k2)
        y = k1 * x * 1.0 + b1 * 1.0
        return [x, y]

    def shorten_line(sx, sy, ex, ey, distance, direction):
        # 朝direction方向延长或缩短distance
        h = ((ex - sx) ** 2 + (ey - sy) ** 2) ** 0.5
        y = distance * (abs(ey - sy) / h)
        x = distance * (abs(ex - sx) / h)
        if (ex - sx) > 0:
            x = -x
        if (ey - sy) > 0:
            y = -y
        rsx, rsy, rex, rey = sx, sy, ex, ey
        if direction in ["W", "S"]:
            if (sx + sy) < (ex + ey):
                rsx, rsy = sx - x, sy - y
            else:
                rex, rey = ex + x, ey + y
        else:
            if (sx + sy) < (ex + ey):
                rex, rey = ex + x, ey + y
            else:
                rsx, rsy = sx - x, sy - y
        return rsx, rsy, rex, rey
        # return sx - x, sy - y, ex + x, ey + y

    id_to_direction = {}
    for i in wall_lines:
        wallID = i.getAttribute("WallId")
        direction = i.getAttribute("direction")
        id_to_direction[wallID] = direction

    # 处理json数据
    direction_move_values = {}
    coordinates = []
    # wall_bump_mapping = {}
    for a_wall_id in json_data["criticalPoint"]:
        if a_wall_id in id_to_direction:
            a_wall_list = json_data["criticalPoint"][a_wall_id]
            raised_value = 0
            a_coordinate = None
            walls_dict[a_wall_id] = {}
            bump_mapping_list = []
            for a_wall in a_wall_list:
                a_value = a_wall["value"]
                point3d = a_wall["point3d"]
                point3d[0], point3d[1] = srotate(rad, point3d[0], point3d[1])
                if a_value > raised_value:
                    raised_value = a_value
                    a_coordinate = point3d
                bump_mapping_list.append({"point3d": point3d, "raise": a_value})
            walls_dict[a_wall_id]["bump_mapping_list"] = bump_mapping_list
            # wall_bump_mapping[a_wall_id] = {"point3d": a_coordinate, "raise": raised_value}
            if a_coordinate:
                coordinates.append(a_coordinate)
            direction_move_values[id_to_direction[a_wall_id]] = raised_value

    max_box_lines = []
    for i in wall_lines:
        sx = float(i.getAttribute("StartX"))
        sy = float(i.getAttribute("StartY"))
        ex = float(i.getAttribute("EndX"))
        ey = float(i.getAttribute("EndY"))
        wallID = i.getAttribute("WallId")
        direction = i.getAttribute("direction")
        dim = msp.add_aligned_dim(p1=(sx, sy), p2=(ex, ey), distance=-200, override={
            'dimtxsty': 'Standard',
            'dimtxt': 35,
            'dimclrt': 1,
            'dimclrd': 4,
            'dimtsz': 0,  # set tick size to 0 to enable arrow usage
            'dimasz': 45,  # arrow size in drawing units
            'dimblk': "EZ_ARROW_FILLED",
            'dimclre': 4,
            'dimexe': 30,  # length above dimension line
            'dimdsep': ord('.'),
            'dimrnd': 0.01,  # 四舍五入
            'dimpost': '<> mm'
        })
        # dim.set_tick(size=25)
        dim.render()
        msp.add_line((sx, sy), (ex, ey), dxfattribs={'layer': '0'})
        distance = -direction_move_values[direction]
        # if direction in ['N', 'E']:
        #     distance = -distance
        old_line = Line([[sx, sy], [ex, ey]])
        sx, sy, ex, ey = move_line(sx, sy, ex, ey, distance=distance, direction=direction)
        # if direction in ["E", "W"]:
        #     sx, sy, ex, ey = shorten_line(sx, sy, ex, ey, distance=direction_move_values["S"], direction="S")
        #     sx, sy, ex, ey = shorten_line(sx, sy, ex, ey, distance=direction_move_values["N"], direction="N")
        # else:
        #     sx, sy, ex, ey = shorten_line(sx, sy, ex, ey, distance=direction_move_values["E"], direction="E")
        #     sx, sy, ex, ey = shorten_line(sx, sy, ex, ey, distance=direction_move_values["W"], direction="W")
        a_line = Line([[sx, sy], [ex, ey]])
        a_line.old_line = old_line
        a_line.direction = direction
        a_line.id = wallID
        # if a_line.id in wall_bump_mapping:
        #     a_line.offset = wall_bump_mapping[a_line.id]["raise"]
        #     a_line.bump_point = wall_bump_mapping[a_line.id]["point3d"]
        if a_line.id in walls_dict:
            a_line.wall_info = walls_dict[a_line.id]
        # a_line.max_point =
        max_box_lines.append(a_line)

    base_line = get_longest_line(max_box_lines)

    for a_line in max_box_lines:
        a_line.a, a_line.b, a_line.angle = _get_a_b_by_line(a_line)
        out_points = [a_line.old_line[0], a_line.old_line[1]]
        # print(a_line.wall_info, a_line.id)
        for a_bump_mapping in a_line.wall_info["bump_mapping_list"]:
            offset = a_bump_mapping['raise']
            bump_point = a_bump_mapping['point3d']
            a_out_point = move_vertical_line_point(a_line, bump_point, offset, a_line.direction)
            a_out_point = point_to_line(a_out_point[0], a_out_point[1], a_line.a, a_line.b)
            out_points.append(a_out_point)
        a_line.out_points = out_points

    def get_rotate_point(line, la):
        # 得到旋转点
        zf = 1
        if line.direction == "N":
            zf = -zf
        elif line.direction == "W" and la > 0 and la != float("inf"):
            zf = -zf
        elif line.direction == "E" and (la < 0 or la == float("inf")):
            zf = -zf
        # print(zf)
        # if line.a != 0:
        #     zf = line.a / abs(line.a)
        # if line.a*la < 0:
        #     zf = -zf
        # angle_y = abs(line.angle)
        # if angle_y<math.pi/8 and line.a*la < 0:
        # if line.b < line.old_line.b:
        #     zf = -zf
        max_b = -float("inf")
        rotate_point = None
        for a_out_point in line.out_points:
            out_b = get_b_by_a_and_point(a_out_point, la) * zf
            if out_b > max_b:
                max_b = out_b
                rotate_point = a_out_point
        return rotate_point

    # print(0, max_box_lines)
    # 绕midpoint旋转到与base_line垂直或平行
    for a_line in max_box_lines:
        # midpoint = get_midpoint_by_line(a_line)  # 线段中点
        if abs(a_line.angle % math.pi - base_line.angle % math.pi) < math.pi / 8:
            la = base_line.a
        # elif abs(abs(a_line.angle - base_line.angle) - math.pi) < math.pi / 8:
        else:
            if base_line.a != 0:
                la = -1 / base_line.a
            else:
                la = float('inf')
        # else:
        #     continue

        bump_point = get_rotate_point(a_line, la)
        lb = get_b_by_a_and_point(bump_point, la)  # 得到穿过点midpoint的直线斜率la和y截距lb
        a_line = line_to_line(a_line, la, lb)  # 线段映射到该直线上，该直线与最长线段垂直或平行

    # print(1, len(max_box_lines), max_box_lines)
    for i in itertools.combinations(max_box_lines, 2):
        line1, line2 = i[0], i[1]
        merge_point(line1, line2)
    # print(2, len(max_box_lines), max_box_lines)
    for a_line in max_box_lines:
        msp.add_line(a_line[0], a_line[1], dxfattribs={'layer': 'max_box', "color": 1})
        # print(a_line, float("inf"))
        dim = msp.add_aligned_dim(p1=a_line[0], p2=a_line[1], distance=150, override={
            'dimtxsty': 'Standard',
            'dimtxt': 35,
            'dimclrt': 1,
            'dimclrd': 2,
            'dimtsz': 0,  # set tick size to 0 to enable arrow usage
            'dimasz': 45,  # arrow size in drawing units
            'dimblk': "EZ_ARROW_FILLED",
            'dimclre': 2,
            'dimexe': 30,  # length above dimension line
            'dimdsep': ord('.'),
            'dimrnd': 0.01,  # 四舍五入
            'dimpost': '<> mm'
        }, dxfattribs={'layer': 'max_box'})
        dim.render()

    for point in coordinates:
        # x, y = srotate(rad, point[0], point[1])
        x, y = point[0], point[1]
        msp.add_circle((x, y), radius=8, dxfattribs={'layer': '0', "color": 2})
        msp.add_point((x, y), dxfattribs={'layer': '0', "color": 2})

    try:
        DoorInfo = rootNode.getElementsByTagName("DoorInfo")[0]
        door_lines = DoorInfo.getElementsByTagName("DoorData")
        my_layer = doc.layers.new('Door')
        deep = 10
        for i in door_lines:
            sx = float(i.getAttribute("StartX"))
            sy = float(i.getAttribute("StartY"))
            ex = float(i.getAttribute("EndX"))
            ey = float(i.getAttribute("EndY"))
            # msp.add_line((sx, sy), (ex, ey), dxfattribs={'layer': 'Door'})
            h = ((ex - sx) ** 2 + (ey - sy) ** 2) ** 0.5
            f = ((sx + ex) / 2, (sy + ey) / 2)
            x, y = sx, sy
            if (ey - ex) - (sy - sx) > 0:
                x, y = ex, ey
            if (ex - sx) != 0:
                q = math.atan((ey - sy) / (ex - sx)) * (180 / math.pi)
            else:
                q = math.pi * 0.5 * (180 / math.pi)
            random_scale = h / 100
            msp.add_blockref('door', f, dxfattribs={
                'layer': 'Door',
                'xscale': random_scale,
                'yscale': random_scale,
                'rotation': q
            })
    except:
        pass

    WinInfos = rootNode.getElementsByTagName("WinInfo")
    winInfo = WinInfos[0] if WinInfos else None
    if winInfo != None:
        Win_lines = winInfo.getElementsByTagName("WinData")
        my_layer = doc.layers.new('Win')
        deep = 30
        # print(Win_lines)
        for i in Win_lines:
            sx = float(i.getAttribute("StartX"))
            sy = float(i.getAttribute("StartY"))
            ex = float(i.getAttribute("EndX"))
            ey = float(i.getAttribute("EndY"))
            h = ((ex - sx) ** 2 + (ey - sy) ** 2) ** 0.5
            # print(h)
            if (ex - sx) != 0:
                q = math.atan((ey - sy) / (ex - sx)) * (180 / math.pi)
            else:
                q = math.pi * 0.5 * (180 / math.pi)
            msp.add_blockref('win', ((sx + ex) / 2, (sy + ey) / 2), dxfattribs={
                'layer': 'Win',
                'xscale': h,
                'yscale': deep,
                'rotation': q
            })

    # doc.saveas(r'roomcontour2.dxf')
    doc.saveas(dxf_filename)
    dxf_object_name = xml.replace(".xml", ".dxf")
    try:
        result = bucket.put_object_from_file(dxf_object_name, dxf_filename)
    except:
        res['statusCode'] = 500
        res['message'] = "Upload ifc fail"
        return res
    res['statusCode'] = result.status
    res['filePath'] = dxf_object_name
    os.remove(filename)
    os.remove(dxf_filename)
    if os.path.exists(zip_filename):
        os.remove(zip_filename)
    return res


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/xml2ifc")
async def xml2ifc(xml: str):
    res = await get_ifc(xml)
    return res


@app.get("/xml2kjl")
async def xml2kjl(xml: str):
    res = await get_kjl_json(xml)
    return res


@app.get("/xml2dxf")
async def xml2dxf(xml: str):
    res = await get_dxf(xml)
    return res


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app=app, host="127.0.0.1", port=8080)
